package demoMod.anm2editor.model;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Disposable;
import demoMod.anm2editor.enums.LayerType;
import demoMod.anm2editor.fonts.FontKeys;
import demoMod.anm2editor.ui.*;
import demoMod.anm2editor.utils.IdGenerator;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.helpers.FontHelper;
import demoMod.gdxform.interfaces.Element;
import demoMod.gdxform.ui.GTextField;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class Project implements Disposable {
    private static final Logger log = LogManager.getLogger(Project.class);
    public static Project currentProject = getBlankProject();

    private String filename;
    private String filePath; //anm2文件所在的文件夹路径，而不是anm2文件的路径
    private String author;
    private int fps;
    private int version;
    private Date createTime;

    private Animation currentAnimation;
    private List<Animation> animations;
    private List<Track> tracks;
    private List<Event> events;
    private final Map<Integer, SpriteSheet> spriteSheets = new LinkedHashMap<>();

    public static void saveProject(String anm2path) throws IOException {
        log.info("Saving project...");
        Document doc = DocumentHelper.createDocument();

        org.dom4j.Element root = doc.addElement("AnimatedActor");

        org.dom4j.Element info = root.addElement("Info");

        log.info("Saving project info...");
        info.addAttribute("CreatedBy", currentProject.getAuthor());
        Date createTime = currentProject.getCreateTime();
        if (createTime != null) {
            info.addAttribute("CreatedOn", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(createTime));
        }
        info.addAttribute("Fps", Integer.toString(currentProject.getFps()));
        info.addAttribute("Version", Integer.toString(currentProject.getVersion()));

        org.dom4j.Element content = root.addElement("Content");

        log.info("Saving sprite sheets...");
        org.dom4j.Element spriteSheets = content.addElement("Spritesheets");
        for (Map.Entry<Integer, SpriteSheet> e : currentProject.spriteSheets.entrySet()) {
            org.dom4j.Element spriteSheet = spriteSheets.addElement("Spritesheet");
            spriteSheet.addAttribute("Path", e.getValue().getPath());
            spriteSheet.addAttribute("Id", e.getKey().toString());
        }

        log.info("Saving layers...");
        org.dom4j.Element layers = content.addElement("Layers");
        List<Track> trackList = currentProject.tracks.stream().filter(track -> track.getLayerType() == LayerType.LAYER).sorted(Comparator.comparingInt(Track::getId)).collect(Collectors.toList());
        for (Track track : trackList) {
            org.dom4j.Element layer = layers.addElement("Layer");
            layer.addAttribute("Name", track.getName());
            layer.addAttribute("Id", Integer.toString(track.getId()));
            layer.addAttribute("SpritesheetId", Integer.toString(track.getSpriteSheetId()));
        }

        org.dom4j.Element nulls = content.addElement("Nulls");
        trackList = currentProject.tracks.stream().filter(track -> track.getLayerType() == LayerType.NULL).sorted(Comparator.comparingInt(Track::getId)).collect(Collectors.toList());
        for (Track track : trackList) {
            org.dom4j.Element layer = nulls.addElement("Null");
            layer.addAttribute("Id", Integer.toString(track.getId()));
            layer.addAttribute("Name", track.getName());
        }

        log.info("Saving events...");
        org.dom4j.Element events = content.addElement("Events");
        for (Event event : currentProject.events) {
            org.dom4j.Element eventElement = events.addElement("Event");
            eventElement.addAttribute("Id", Integer.toString(event.getId()));
            eventElement.addAttribute("Name", event.getName());
        }

        log.info("Saving animations...");
        org.dom4j.Element animations = root.addElement("Animations");
        Animation defaultAnimation = null;
        for (Animation animation : currentProject.animations) {
            if (animation.isDefaultAnimation()) {
                defaultAnimation = animation;
                break;
            }
        }
        animations.addAttribute("DefaultAnimation", defaultAnimation == null ? "" : defaultAnimation.getName());

        for (Animation animation : currentProject.animations) {
            log.info("Saving animation - {}", animation.getName());
            org.dom4j.Element animationElement = animations.addElement("Animation");
            animationElement.addAttribute("Name", animation.getName());
            animationElement.addAttribute("FrameNum", Integer.toString(animation.getAnimationLength()));
            animationElement.addAttribute("Loop", animation.isLoop() ? "True" : "False");

            org.dom4j.Element rootAnimation = animationElement.addElement("RootAnimation");
            org.dom4j.Element rootFrame = rootAnimation.addElement("Frame");
            rootFrame.addAttribute("XPosition", "0");
            rootFrame.addAttribute("YPosition", "0");
            rootFrame.addAttribute("Delay", Integer.toString(animation.getAnimationLength()));
            rootFrame.addAttribute("Visible", "True");
            rootFrame.addAttribute("XScale", "100");
            rootFrame.addAttribute("YScale", "100");
            rootFrame.addAttribute("RedTint", "255");
            rootFrame.addAttribute("GreenTint", "255");
            rootFrame.addAttribute("BlueTint", "255");
            rootFrame.addAttribute("AlphaTint", "255");
            rootFrame.addAttribute("RedOffset", "0");
            rootFrame.addAttribute("GreenOffset", "0");
            rootFrame.addAttribute("BlueOffset", "0");
            rootFrame.addAttribute("Rotation", "0");
            rootFrame.addAttribute("Interpolated", "False");

            org.dom4j.Element layerAnimations = animationElement.addElement("LayerAnimations");
            List<Track> tracks1 = currentProject.tracks.stream().filter(track -> track.getLayerType() == LayerType.LAYER).collect(Collectors.toList());
            Collections.reverse(tracks1);
            log.info("--- Layer animation ---");
            for (Track track : tracks1) {
                log.info("Saving track - {}", track.getName());
                org.dom4j.Element layerAnimation = layerAnimations.addElement("LayerAnimation");
                layerAnimation.addAttribute("LayerId", Integer.toString(track.getId()));
                layerAnimation.addAttribute("Visible", track.getContent(animation.getName()).isVisible() ? "True" : "False");
                for (KeyFrame keyFrame : track.getContent(animation.getName()).getKeyFrames()) {
                    log.info("Saving keyframe - {}", keyFrame);
                    org.dom4j.Element frame = layerAnimation.addElement("Frame");
                    frame.addAttribute("XPosition", Float.toString(keyFrame.xPosition));
                    frame.addAttribute("YPosition", Float.toString(keyFrame.yPosition));
                    frame.addAttribute("XPivot", Float.toString(keyFrame.xPivot));
                    frame.addAttribute("YPivot", Float.toString(keyFrame.yPivot));
                    frame.addAttribute("Width", Integer.toString(keyFrame.width));
                    frame.addAttribute("Height", Integer.toString(keyFrame.height));
                    frame.addAttribute("XScale", Float.toString(keyFrame.xScale));
                    frame.addAttribute("YScale", Float.toString(keyFrame.yScale));
                    frame.addAttribute("Delay", Integer.toString(keyFrame.delay));
                    frame.addAttribute("Visible", keyFrame.visible ? "True" : "False");
                    frame.addAttribute("XCrop", Integer.toString(keyFrame.xCrop));
                    frame.addAttribute("YCrop", Integer.toString(keyFrame.yCrop));
                    frame.addAttribute("RedTint", Integer.toString((int) (keyFrame.tint.r * 255.0F)));
                    frame.addAttribute("GreenTint", Integer.toString((int) (keyFrame.tint.g * 255.0F)));
                    frame.addAttribute("BlueTint", Integer.toString((int) (keyFrame.tint.b * 255.0F)));
                    frame.addAttribute("AlphaTint", Integer.toString((int) (keyFrame.tint.a * 255.0F)));
                    frame.addAttribute("RedOffset", Integer.toString((int) (keyFrame.colorOffset.r * 255.0F)));
                    frame.addAttribute("GreenOffset", Integer.toString((int) (keyFrame.colorOffset.g * 255.0F)));
                    frame.addAttribute("BlueOffset", Integer.toString((int) (keyFrame.colorOffset.b * 255.0F)));
                    frame.addAttribute("Rotation", Float.toString(keyFrame.rotation));
                    frame.addAttribute("Interpolated", keyFrame.interpolated ? "True" : "False");
                }
            }

            org.dom4j.Element nullAnimations = animationElement.addElement("NullAnimations");
            List<Track> tracks2 = currentProject.tracks.stream().filter(track -> track.getLayerType() == LayerType.NULL).collect(Collectors.toList());
            Collections.reverse(tracks2);
            log.info("--- Null animation ---");
            for (Track track : tracks2) {
                log.info("Saving track - {}", track.getName());
                org.dom4j.Element nullAnimation = nullAnimations.addElement("NullAnimation");
                nullAnimation.addAttribute("NullId", Integer.toString(track.getId()));
                nullAnimation.addAttribute("Visible", track.getContent(animation.getName()).isVisible() ? "True" : "False");
                for (KeyFrame keyFrame : track.getContent(animation.getName()).getKeyFrames()) {
                    log.info("Saving keyframe - {}", keyFrame);
                    org.dom4j.Element frame = nullAnimation.addElement("Frame");
                    frame.addAttribute("XPosition", Float.toString(keyFrame.xPosition));
                    frame.addAttribute("YPosition", Float.toString(keyFrame.yPosition));
                    frame.addAttribute("XPivot", Float.toString(keyFrame.xPivot));
                    frame.addAttribute("YPivot", Float.toString(keyFrame.yPivot));
                    frame.addAttribute("Width", Integer.toString(keyFrame.width));
                    frame.addAttribute("Height", Integer.toString(keyFrame.height));
                    frame.addAttribute("Delay", Integer.toString(keyFrame.delay));
                    frame.addAttribute("Visible", keyFrame.visible ? "True" : "False");
                    frame.addAttribute("XCrop", Integer.toString(keyFrame.xCrop));
                    frame.addAttribute("YCrop", Integer.toString(keyFrame.yCrop));
                    frame.addAttribute("XScale", Float.toString(keyFrame.xScale));
                    frame.addAttribute("YScale", Float.toString(keyFrame.yScale));
                    frame.addAttribute("RedTint", Integer.toString((int) (keyFrame.tint.r * 255.0F)));
                    frame.addAttribute("GreenTint", Integer.toString((int) (keyFrame.tint.g * 255.0F)));
                    frame.addAttribute("BlueTint", Integer.toString((int) (keyFrame.tint.b * 255.0F)));
                    frame.addAttribute("AlphaTint", Integer.toString((int) (keyFrame.tint.a * 255.0F)));
                    frame.addAttribute("RedOffset", Integer.toString((int) (keyFrame.colorOffset.r * 255.0F)));
                    frame.addAttribute("GreenOffset", Integer.toString((int) (keyFrame.colorOffset.g * 255.0F)));
                    frame.addAttribute("BlueOffset", Integer.toString((int) (keyFrame.colorOffset.b * 255.0F)));
                    frame.addAttribute("Rotation", Float.toString(keyFrame.rotation));
                    frame.addAttribute("Interpolated", keyFrame.interpolated ? "True" : "False");
                }
            }

            log.info("--- Triggers ---");
            org.dom4j.Element triggers = animationElement.addElement("Triggers");
            log.info("Saving triggers...");
            for (Event event : currentProject.events) {
                log.info("Trigger name: {}", event.getName());
                Map<String, List<Integer>> usages = event.getUsages();
                for (Integer atFrame : usages.getOrDefault(animation.getName(), new ArrayList<>())) {
                    org.dom4j.Element trigger = triggers.addElement("Trigger");
                    trigger.addAttribute("EventId", Integer.toString(event.getId()));
                    trigger.addAttribute("AtFrame", Integer.toString(atFrame));
                }
            }
        }
        File file = new File(anm2path);
        if (!file.exists()) {
            log.info("Creating file...");
            file.createNewFile();
            log.info("File created.");
        }
        OutputStream os = new FileOutputStream(anm2path);
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setEncoding("UTF-8");
        XMLWriter writer = new XMLWriter(os, format);
        log.info("Writing data...");
        writer.write(doc);
        writer.flush();
        writer.close();
        log.info("Done.");
    }

    public static void openProject(File anm2file) {
        log.info("Opening project...");
        log.info("Disposing previous project...");
        currentProject.dispose();
        log.info("Disposed previous project.");
        currentProject = getBlankProject();
        SAXReader reader = new SAXReader();
        Document doc = null;
        try {
            log.info("Loading anm2 file...");
            doc = reader.read(anm2file);
            log.info("File loaded.");
        } catch (DocumentException e) {
            log.error("Read file failed!", e);
        }
        if (doc != null) {
            log.info("Parsing file...");
            currentProject.filename = anm2file.getName();
            currentProject.filePath = anm2file.getParentFile().getAbsolutePath();
            org.dom4j.Element root = doc.getRootElement();
            org.dom4j.Element animationInfo = root.element("Info");
            currentProject.author = animationInfo.attributeValue("CreatedBy");
            ((GTextField) FormManager.getInstance().getContainerById(StatusBar.ID).getElementById("author")).setText(currentProject.author);
            try {
                currentProject.createTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(animationInfo.attributeValue("CreatedOn"));
            } catch (ParseException e) {
                log.warn("Parse create time failed!", e);
            }
            currentProject.fps = Integer.parseInt(animationInfo.attributeValue("Fps"));
            currentProject.version = Integer.parseInt(animationInfo.attributeValue("Version"));
            log.info("========== Project info ==========");
            log.info("File path: {}", currentProject.filePath + File.separator + currentProject.filename);
            log.info("Author: {}", currentProject.author);
            log.info("Create time: {}", currentProject.createTime);
            log.info("Fps: {}", currentProject.fps);
            log.info("Version: {}", currentProject.version);
            log.info("==================================");
            org.dom4j.Element content = root.element("Content");
            org.dom4j.Element spriteSheets = content.element("Spritesheets");
            Iterator<org.dom4j.Element> spriteSheetsIt = spriteSheets.elementIterator();
            int maxSpriteSheetId = Integer.MIN_VALUE;
            log.info("Loading sprite sheets...");
            while (spriteSheetsIt.hasNext()) {
                org.dom4j.Element spriteSheet = spriteSheetsIt.next();
                String imgPath = spriteSheet.attributeValue("Path");
                Texture img;
                try { //先尝试绝对路径
                    img = new Texture(Gdx.files.absolute(imgPath));
                    log.info("Find texture {}", imgPath);
                } catch (Exception e) {
                    try {
                        img = new Texture(Gdx.files.absolute(anm2file.getParentFile().getAbsolutePath() + File.separator + imgPath));
                        log.info("Find texture {}", anm2file.getParentFile().getAbsolutePath() + File.separator + imgPath);
                    } catch (Exception e1) {
                        log.warn("Load texture {} failed, replaced with pure color rectangle.", Gdx.files.absolute(anm2file.getParentFile().getAbsolutePath() + File.separator + imgPath));
                        log.warn("Reason:", e1);
                        Pixmap pixmap = new Pixmap(20, 20, Pixmap.Format.RGBA8888);
                        pixmap.setColor(Color.RED.cpy());
                        pixmap.fill();
                        img = new Texture(pixmap);
                    }
                }
                SpriteSheet spriteSheet1 = new SpriteSheet();
                spriteSheet1.setSprite(img);
                spriteSheet1.setPath(imgPath);
                spriteSheet1.loadAtlasData();
                int id = Integer.parseInt(spriteSheet.attributeValue("Id"));
                currentProject.addSpriteSheet(id, spriteSheet1);
                if (maxSpriteSheetId < id) {
                    maxSpriteSheetId = id;
                }
            }
            IdGenerator.setSpriteSheetId(maxSpriteSheetId + 1);
            org.dom4j.Element layers = content.element("Layers");
            Iterator<org.dom4j.Element> layersIt = layers.elementIterator();
            currentProject.tracks = new ArrayList<>();
            Map<Integer, Track> trackList = new LinkedHashMap<>();
            Map<Integer, Track> nullList = new LinkedHashMap<>();
            int maxLayerId = Integer.MIN_VALUE;
            log.info("Finding layers...");
            while (layersIt.hasNext()) {
                org.dom4j.Element layer = layersIt.next();
                Track track = new Track(LayerType.LAYER);
                track.setId(Integer.parseInt(layer.attributeValue("Id")));
                track.setName(layer.attributeValue("Name"));
                track.setSpriteSheetId(Integer.parseInt(layer.attributeValue("SpritesheetId")));
                trackList.put(track.getId(), track);
                if (track.getId() > maxLayerId) {
                    maxLayerId = track.getId();
                }
                log.info("Find layer: {}", track.getName());
            }
            IdGenerator.setTrackId(maxLayerId + 1);
            org.dom4j.Element nulls = content.element("Nulls");
            Iterator<org.dom4j.Element> nullsIt = nulls.elementIterator();
            int maxNullId = Integer.MIN_VALUE;
            while (nullsIt.hasNext()) {
                org.dom4j.Element nullLayer = nullsIt.next();
                Track track = new Track(LayerType.NULL);
                track.setId(Integer.parseInt(nullLayer.attributeValue("Id")));
                track.setName(nullLayer.attributeValue("Name"));
                nullList.put(track.getId(), track);
                if (track.getId() > maxNullId) {
                    maxNullId = track.getId();
                }
                log.info("Find null layer: {}", track.getName());
            }
            IdGenerator.setNullId(maxNullId + 1);
            org.dom4j.Element events = content.element("Events");
            Iterator<org.dom4j.Element> eventsIt = events.elementIterator();
            Map<Integer, Event> eventMap = new HashMap<>();
            int maxEventId = Integer.MIN_VALUE;
            log.info("Finding triggers...");
            while (eventsIt.hasNext()) {
                org.dom4j.Element event = eventsIt.next();
                Event event1 = new Event();
                event1.setId(Integer.parseInt(event.attributeValue("Id")));
                event1.setName(event.attributeValue("Name"));
                event1.setUsages(new HashMap<>());
                currentProject.events.add(event1);
                eventMap.put(event1.getId(), event1);
                if (event1.getId() > maxEventId) {
                    maxEventId = event1.getId();
                }
                log.info("Find trigger: {}, id: {}", event1.getName(), event1.getId());
            }
            IdGenerator.setEventId(maxEventId + 1);
            Iterator<org.dom4j.Element> animationsIt = root.element("Animations").elementIterator();

            String defaultAnimationName = root.element("Animations").attributeValue("DefaultAnimation");
            currentProject.animations = new ArrayList<>();
            log.info("Loading animations...");
            while (animationsIt.hasNext()) {
                org.dom4j.Element animationElement = animationsIt.next();
                Animation animation = new Animation();
                animation.setAnimationLength(Integer.parseInt(animationElement.attributeValue("FrameNum")));
                animation.setName(animationElement.attributeValue("Name"));
                log.info("Find animation: {}, Length: {}", animation.getName(), animation.getAnimationLength());
                if (animation.getName().equals(defaultAnimationName)) {
                    animation.setDefaultAnimation(true);
                    currentProject.setCurrentAnimation(animation);
                    log.info("Animation {} is default animation.", animation.getName());
                }
                animation.setLoop(Boolean.parseBoolean(animationElement.attributeValue("Loop")));
                org.dom4j.Element layerAnimations = animationElement.element("LayerAnimations");
                Iterator<org.dom4j.Element> layerAnimationsIt = layerAnimations.elementIterator();
                log.info("Loading layer animations...");
                while (layerAnimationsIt.hasNext()) {
                    org.dom4j.Element layerAnimationElement = layerAnimationsIt.next();
                    Iterator<org.dom4j.Element> layerAnimationIt = layerAnimationElement.elementIterator();
                    List<KeyFrame> keyFrames = new ArrayList<>();
                    log.info("Loading keyframes in layer {} for animation {}...", trackList.get(Integer.parseInt(layerAnimationElement.attributeValue("LayerId"))).getName(), animation.getName());
                    while (layerAnimationIt.hasNext()) {
                        org.dom4j.Element frameElement = layerAnimationIt.next();
                        KeyFrame frame = new KeyFrame();
                        frame.xPosition = Float.parseFloat(frameElement.attributeValue("XPosition"));
                        frame.yPosition = Float.parseFloat(frameElement.attributeValue("YPosition"));
                        frame.xPivot = Float.parseFloat(frameElement.attributeValue("XPivot"));
                        frame.yPivot = Float.parseFloat(frameElement.attributeValue("YPivot"));
                        frame.xCrop = Integer.parseInt(frameElement.attributeValue("XCrop"));
                        frame.yCrop = Integer.parseInt(frameElement.attributeValue("YCrop"));
                        frame.width = Integer.parseInt(frameElement.attributeValue("Width"));
                        frame.height = Integer.parseInt(frameElement.attributeValue("Height"));
                        frame.xScale = Float.parseFloat(frameElement.attributeValue("XScale"));
                        frame.yScale = Float.parseFloat(frameElement.attributeValue("YScale"));
                        frame.delay = Integer.parseInt(frameElement.attributeValue("Delay"));
                        frame.visible = Boolean.parseBoolean(frameElement.attributeValue("Visible"));
                        Color tint = new Color();
                        tint.r = Float.parseFloat(frameElement.attributeValue("RedTint")) / 255.0F;
                        tint.g = Float.parseFloat(frameElement.attributeValue("GreenTint")) / 255.0F;
                        tint.b = Float.parseFloat(frameElement.attributeValue("BlueTint")) / 255.0F;
                        tint.a = Float.parseFloat(frameElement.attributeValue("AlphaTint")) / 255.0F;
                        frame.tint = tint;
                        Color colorOffset = new Color();
                        colorOffset.r = Float.parseFloat(frameElement.attributeValue("RedOffset")) / 255.0F;
                        colorOffset.g = Float.parseFloat(frameElement.attributeValue("GreenOffset")) / 255.0F;
                        colorOffset.b = Float.parseFloat(frameElement.attributeValue("BlueOffset")) / 255.0F;
                        frame.colorOffset = colorOffset;
                        frame.rotation = Float.parseFloat(frameElement.attributeValue("Rotation"));
                        if (frameElement.attributeValue("Interpolated") != null) {
                            frame.interpolated = Boolean.parseBoolean(frameElement.attributeValue("Interpolated"));
                        } else {
                            frame.interpolated = false;
                        }
                        keyFrames.add(frame);
                        log.info("Loaded keyframe - {}", frame);
                    }
                    TrackContent trackContent = new TrackContent();
                    trackContent.setKeyFrames(keyFrames);
                    trackContent.setVisible(Boolean.parseBoolean(layerAnimationElement.attributeValue("Visible")));
                    Track track = trackList.get(Integer.parseInt(layerAnimationElement.attributeValue("LayerId")));
                    track.addContent(animation.getName(), trackContent);
                    if (!currentProject.tracks.contains(track)) currentProject.tracks.add(track);
                }
                org.dom4j.Element nullAnimations = animationElement.element("NullAnimations");
                Iterator<org.dom4j.Element> nullAnimationsIt = nullAnimations.elementIterator();
                log.info("Loading null animations...");
                while (nullAnimationsIt.hasNext()) {
                    org.dom4j.Element nullAnimationElement = nullAnimationsIt.next();
                    Iterator<org.dom4j.Element> nullAnimationIt = nullAnimationElement.elementIterator();
                    List<KeyFrame> keyFrames = new ArrayList<>();
                    log.info("Loading keyframes in layer {} for animation {}...", nullList.get(Integer.parseInt(nullAnimationElement.attributeValue("NullId"))).getName(), animation.getName());
                    while (nullAnimationIt.hasNext()) {
                        org.dom4j.Element frameElement = nullAnimationIt.next();
                        KeyFrame frame = new KeyFrame();
                        frame.xPosition = Float.parseFloat(frameElement.attributeValue("XPosition"));
                        frame.yPosition = Float.parseFloat(frameElement.attributeValue("YPosition"));
                        try {
                            frame.xPivot = Float.parseFloat(frameElement.attributeValue("XPivot"));
                            frame.yPivot = Float.parseFloat(frameElement.attributeValue("YPivot"));
                            frame.xCrop = Integer.parseInt(frameElement.attributeValue("XCrop"));
                            frame.yCrop = Integer.parseInt(frameElement.attributeValue("YCrop"));
                            frame.width = Integer.parseInt(frameElement.attributeValue("Width"));
                            frame.height = Integer.parseInt(frameElement.attributeValue("Height"));
                        } catch (NullPointerException e) {
                            log.warn("Older version of project. Will ignore these attributes: XPivot YPivot XCrop YCrop Width Height");
                        }
                        frame.xScale = Float.parseFloat(frameElement.attributeValue("XScale"));
                        frame.yScale = Float.parseFloat(frameElement.attributeValue("YScale"));
                        frame.delay = Integer.parseInt(frameElement.attributeValue("Delay"));
                        frame.visible = Boolean.parseBoolean(frameElement.attributeValue("Visible"));
                        Color tint = new Color();
                        tint.r = Float.parseFloat(frameElement.attributeValue("RedTint")) / 255.0F;
                        tint.g = Float.parseFloat(frameElement.attributeValue("GreenTint")) / 255.0F;
                        tint.b = Float.parseFloat(frameElement.attributeValue("BlueTint")) / 255.0F;
                        tint.a = Float.parseFloat(frameElement.attributeValue("AlphaTint")) / 255.0F;
                        frame.tint = tint;
                        Color colorOffset = new Color();
                        colorOffset.r = Float.parseFloat(frameElement.attributeValue("RedOffset")) / 255.0F;
                        colorOffset.g = Float.parseFloat(frameElement.attributeValue("GreenOffset")) / 255.0F;
                        colorOffset.b = Float.parseFloat(frameElement.attributeValue("BlueOffset")) / 255.0F;
                        frame.colorOffset = colorOffset;
                        frame.rotation = Float.parseFloat(frameElement.attributeValue("Rotation"));
                        if (frameElement.attributeValue("Interpolated") != null) {
                            frame.interpolated = Boolean.parseBoolean(frameElement.attributeValue("Interpolated"));
                        } else {
                            frame.interpolated = false;
                        }
                        keyFrames.add(frame);
                        log.info("Loaded keyframe - {}", frame);
                    }
                    TrackContent trackContent = new TrackContent();
                    trackContent.setKeyFrames(keyFrames);
                    trackContent.setVisible(Boolean.parseBoolean(nullAnimationElement.attributeValue("Visible")));
                    Track track = nullList.get(Integer.parseInt(nullAnimationElement.attributeValue("NullId")));
                    track.addContent(animation.getName(), trackContent);
                    if (!currentProject.tracks.contains(track)) currentProject.tracks.add(track);
                }
                Iterator<org.dom4j.Element> triggersIt = animationElement.element("Triggers").elementIterator();
                log.info("Loading triggers...");
                while (triggersIt.hasNext()) {
                    org.dom4j.Element triggerElement = triggersIt.next();
                    Event event = eventMap.get(Integer.parseInt(triggerElement.attributeValue("EventId")));
                    Map<String, List<Integer>> usages = event.getUsages();
                    if (!usages.containsKey(animation.getName())) {
                        usages.put(animation.getName(), new ArrayList<>());
                    }
                    usages.get(animation.getName()).add(Integer.parseInt(triggerElement.attributeValue("AtFrame")));
                    log.info("Loaded trigger: {}, id: {}", event.getName(), event.getId());
                }
                currentProject.animations.add(animation);
            }
            Collections.reverse(currentProject.tracks);
            if (currentProject.getCurrentAnimation() == null && !currentProject.animations.isEmpty()) {
                currentProject.setCurrentAnimation(currentProject.animations.get(0));
            }
            log.info("Parsed project.");
        }
    }

    public static void clearProject() {
        log.info("Clearing displayed data...");
        Timeline timeline = (Timeline) FormManager.getInstance().getContainerById(Timeline.ID);
        timeline.clearTrack();
        timeline.getScrollPane().setPlainHeight(timeline.getHeight() * 2);
        TrackPane trackPane = (TrackPane) FormManager.getInstance().getContainerById(TrackPane.ID);
        trackPane.clearTrack();
        trackPane.getScrollPane().setPlainHeight(trackPane.getHeight() * 2);
        AnimationPane animationPane = (AnimationPane) FormManager.getInstance().getContainerById(AnimationPane.ID);
        animationPane.clear();
        animationPane.getScrollPane().setPlainHeight(animationPane.getHeight() * 2);
        EventPane eventPane = (EventPane) FormManager.getInstance().getContainerById(EventPane.ID);
        eventPane.clear();
        eventPane.getScrollPane().setPlainHeight(eventPane.getHeight() * 2);
        RegionManager regionManager = (RegionManager) FormManager.getInstance().getContainerById(RegionManager.ID);
        for (Element element : regionManager.getSpriteSheetList().getElements()) {
            element.dispose();
        }
        regionManager.getSpriteSheetList().clear();
        regionManager.getScrollPane().setPlainHeight(regionManager.getHeight());
        regionManager.getScrollPane().getVScrollBar().setProgress(0);
        log.info("Cleared.");
    }

    public static void loadProject() {
        log.info("Preparing data...");
        Animation animation = currentProject.getCurrentAnimation();
        if (animation != null) {
            ((Timeline) FormManager.getInstance().getContainerById(Timeline.ID)).clearTrack();
            ((TrackPane) FormManager.getInstance().getContainerById(TrackPane.ID)).clearTrack();
            for (Track track : currentProject.getTracks()) {
                ((Timeline) FormManager.getInstance().getContainerById(Timeline.ID)).addTrack(animation.getName(), track);
                ((TrackPane)FormManager.getInstance().getContainerById(TrackPane.ID)).addTrack(track);
            }
        }
        ((AnimationPane) FormManager.getInstance().getContainerById(AnimationPane.ID)).clear();
        for (Animation animation1 : currentProject.getAnimations()) {
            ((AnimationPane) FormManager.getInstance().getContainerById(AnimationPane.ID)).addAnimation(animation1);
        }
        ((EventPane) FormManager.getInstance().getContainerById(EventPane.ID)).clear();
        for (Event event : currentProject.getEvents()) {
            ((EventPane) FormManager.getInstance().getContainerById(EventPane.ID)).addEvent(event);
        }
        RegionManager regionManager = (RegionManager) FormManager.getInstance().getContainerById(RegionManager.ID);
        for (Element element : regionManager.getSpriteSheetList().getElements()) {
            element.dispose();
        }
        regionManager.getSpriteSheetList().clear();
        for (Integer id : Project.currentProject.getSpriteSheetIds()) {
            SpriteSheet spriteSheet = Project.currentProject.getSpriteSheet(id);
            SpriteSheetItem item = new SpriteSheetItem(FontHelper.getFont(FontKeys.SIM_HEI_14));
            item.setSpriteSheet(spriteSheet);
            item.setId(Integer.toString(id));
            regionManager.getSpriteSheetList().addItem(item);
        }
        AnimationPane animationPane = (AnimationPane) FormManager.getInstance().getContainerById(AnimationPane.ID);
        while (currentProject.getAnimations().size() * ((Timeline) FormManager.getInstance().getContainerById(Timeline.ID)).getTrackHeight() > animationPane.getScrollPane().getPlainHeight()) {
            animationPane.getScrollPane().setPlainHeight(animationPane.getScrollPane().getPlainHeight() + 3 * ((Timeline) FormManager.getInstance().getContainerById(Timeline.ID)).getTrackHeight());
        }
        Timeline timeline = (Timeline) FormManager.getInstance().getContainerById(Timeline.ID);
        EventPane eventPane = (EventPane) FormManager.getInstance().getContainerById(EventPane.ID);
        while (currentProject.events.size() * timeline.getTrackHeight() > eventPane.getScrollPane().getPlainHeight()) {
            eventPane.getScrollPane().setPlainHeight(eventPane.getScrollPane().getPlainHeight() + timeline.getTrackHeight());
        }
        while (currentProject.getSpriteSheets().size() * regionManager.getSpriteSheetList().getCellHeight() > regionManager.getScrollPane().getPlainHeight()) {
            regionManager.getScrollPane().setPlainHeight(regionManager.getScrollPane().getPlainHeight() + regionManager.getSpriteSheetList().getCellHeight());
        }
        log.info("Done.");
    }

    public static Project getBlankProject() {
        IdGenerator.reset();
        Project ret = new Project();
        ret.setAuthor("Unknown");
        ret.setCreateTime(new Date());
        List<Animation> animations = new ArrayList<>();
        ret.setAnimations(animations);
        ret.setFps(30);
        ret.setEvents(new ArrayList<>());
        List<Track> tracks = new ArrayList<>();
        ret.setTracks(tracks);

        return ret;
    }

    public static boolean isBlankProject() {
        if (!"Unknown".equals(currentProject.getAuthor())) return false;
        if (currentProject.animations == null || !currentProject.animations.isEmpty()) return false;
        if (currentProject.fps != 30) return false;
        if (currentProject.events == null || !currentProject.events.isEmpty()) return false;
        return currentProject.tracks != null && currentProject.tracks.isEmpty();
    }

    private static Project getDemoProject() {
        IdGenerator.reset();
        Project ret = new Project();
        ret.setAuthor("Unknown");
        ret.setCreateTime(new Date());
        List<Animation> animations = new ArrayList<>();
        Animation defaultAnimation = new Animation();
        defaultAnimation.setLoop(true);
        defaultAnimation.setDefaultAnimation(true);
        defaultAnimation.setAnimationLength(30);
        defaultAnimation.setName("idle");

        Animation testAnimation = new Animation();
        testAnimation.setLoop(false);
        testAnimation.setAnimationLength(16);
        testAnimation.setName("test");

        animations.add(defaultAnimation);
        animations.add(testAnimation);
        ret.setAnimations(animations);
        ret.setCurrentAnimation(defaultAnimation);
        ret.setFps(30);
        ret.setEvents(new ArrayList<>());
        List<Track> tracks = new ArrayList<>();
        Track track1 = new Track(LayerType.LAYER);
        track1.setId(IdGenerator.nextTrackId());
        track1.setName("图层 1");
        track1.setSpriteSheetId(0);

        TrackContent content = new TrackContent();

        List<KeyFrame> keyFrames = new ArrayList<>();
        KeyFrame keyFrame = new KeyFrame();
        keyFrame.xPosition = 0;
        keyFrame.yPosition = 0;
        keyFrame.interpolated = true;
        keyFrame.delay = 29;
        keyFrame.width = 20;
        keyFrame.height = 20;
        keyFrame.xPivot = 10;
        keyFrame.yPivot = 10;
        keyFrame.xCrop = 0;
        keyFrame.yCrop = 0;
        keyFrame.xScale = 100.0F;
        keyFrame.yScale = 100.0F;
        keyFrame.tint = Color.GREEN.cpy();
        keyFrame.colorOffset = Color.WHITE.cpy();
        keyFrame.visible = true;
        KeyFrame keyFrame2 = keyFrame.makeCopy();
        keyFrame2.yScale = 2.0F;
        keyFrame2.delay = 1;
        keyFrame2.interpolated = false;
        keyFrame2.tint = Color.RED.cpy();

        keyFrames.add(keyFrame);
        keyFrames.add(keyFrame2);

        content.setKeyFrames(keyFrames);

        track1.addContent(defaultAnimation.getName(), content);
        track1.addContent(testAnimation.getName(), new TrackContent().setKeyFrames(new ArrayList<>()));

        Track track2 = new Track(LayerType.LAYER);
        track2.setId(IdGenerator.nextTrackId());
        track2.setName("图层 2");
        track2.setSpriteSheetId(0);

        TrackContent content2 = new TrackContent();
        List<KeyFrame> keyFrames2 = new ArrayList<>();

        KeyFrame keyFrame3 = keyFrame.makeCopy();
        keyFrame3.tint = Color.WHITE.cpy();
        keyFrame3.delay = 15;

        KeyFrame keyFrame4 = keyFrame3.makeCopy();
        keyFrame4.delay = 14;
        keyFrame4.yPosition = 40.0F;

        KeyFrame keyFrame5 = keyFrame3.makeCopy();
        keyFrame5.delay = 1;
        keyFrame5.interpolated = false;

        keyFrames2.add(keyFrame3);
        keyFrames2.add(keyFrame4);
        keyFrames2.add(keyFrame5);

        content2.setKeyFrames(keyFrames2);
        track2.addContent(defaultAnimation.getName(), content2);
        track2.addContent(testAnimation.getName(), new TrackContent().setKeyFrames(new ArrayList<>()));

        tracks.add(track1);
        tracks.add(track2);

        ret.setTracks(tracks);
        Pixmap pixmap = new Pixmap(20, 20, Pixmap.Format.RGBA8888);
        pixmap.setColor(Color.WHITE.cpy());
        pixmap.fill();
        SpriteSheet spriteSheet = new SpriteSheet();
        spriteSheet.setPath("");
        spriteSheet.setSprite(new Texture(pixmap));
        ret.addSpriteSheet(IdGenerator.nextSpriteSheetId(), spriteSheet);

        List<Event> events = new ArrayList<>();
        Event event1 = new Event();
        event1.setId(IdGenerator.nextEventId());
        event1.setName("Test");
        Map<String, List<Integer>> usages = new HashMap<>();
        usages.put(defaultAnimation.getName(), new ArrayList<>());
        usages.put(testAnimation.getName(), new ArrayList<>());
        event1.setUsages(usages);
        events.add(event1);
        ret.setEvents(events);
        return ret;
    }

    public String getFilename() {
        return filename;
    }

    public void setFilename(String filename) {
        this.filename = filename;
    }

    public String getFilePath() {
        if (filePath == null) {
            return null;
        }
        return filePath.endsWith(File.separator) ? filePath.substring(0, filePath.length() - 1) : filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public int getFps() {
        return fps;
    }

    public void setFps(int fps) {
        this.fps = fps;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Animation getCurrentAnimation() {
        return currentAnimation;
    }

    public void setCurrentAnimation(Animation currentAnimation) {
        this.currentAnimation = currentAnimation;
    }

    public List<Animation> getAnimations() {
        return animations;
    }

    public void setAnimations(List<Animation> animations) {
        this.animations = animations;
    }

    public List<Track> getTracks() {
        return tracks;
    }

    public void setTracks(List<Track> tracks) {
        this.tracks = tracks;
    }

    public List<Event> getEvents() {
        return events;
    }

    public void setEvents(List<Event> events) {
        this.events = events;
    }

    public void addSpriteSheet(int id, SpriteSheet spriteSheet) {
        this.spriteSheets.put(id, spriteSheet);
    }

    public void removeSpriteSheet(int id) {
        this.spriteSheets.remove(id);
    }

    public SpriteSheet getSpriteSheet(int id) {
        return this.spriteSheets.get(id);
    }

    public List<SpriteSheet> getSpriteSheets() {
        return new ArrayList<>(spriteSheets.values());
    }

    public List<Integer> getSpriteSheetIds() {
        List<Integer> ret = new ArrayList<>(this.spriteSheets.keySet());
        ret.sort(Integer::compareTo);
        return ret;
    }

    @Override
    public void dispose() {
        for (Map.Entry<Integer, SpriteSheet> e : this.spriteSheets.entrySet()) {
            if (e.getValue() != null) {
                e.getValue().dispose();
            }
        }
    }
}
